/*
 * topological-hex, a program to compute combinatorial hexahedral meshes.
 *
 * Copyright (C) <2018> <Université catholique de Louvain (UCL), Belgique>
 *
 * List of the contributors to the development, description and complete
 * License: see LICENSE file.
 *
 * This program (topological-hex) is free software:
 * you can redistribute it and/or modify it under the terms
 * of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program (see COPYING file).  If not,
 * see <http://www.gnu.org/licenses/>.
 */

#ifndef CLIENT_H_
#define CLIENT_H_

#include <stdbool.h>
#include "protocol.h"
#include "parallel_dfs.h"

/**
 * @addtogroup distributed
 * @{
 */

/**
 * Stores information used by clients in a distributed system.
 */
typedef struct client {
  int socket;
  bool should_stop;
  subproblem_layout layout;

  /**
   * Buffer large enough for the packets that need to be sent to the server.
   */
  void *buffer;
} client;

/**
 * Connects a client to a server hosted on @p server_name.
 *
 * A username is also sent to the server to help identify this client in
 * logs. This username is the contents of the USER environment variable.
 *
 * Most clients will first wait to receive a memory layout (@ref
 * client_receive_layout), but one of them will send the initial set of
 * subproblems to the server (@ref client_send_layout, @ref
 * client_send_subproblems, @ref client_finish_send_subproblems).
 *
 * Usage of a client looks like this:
 *
 * @code
 * client_init(&client, "localhost");
 *
 * client_receive_layout(&client, &num_vertices, &num_quads, &num_hexes);
 * // allocate memory
 *
 * bool asleep = false;
 * while (client_idle(&client, &asleep, should_stay_idle, data)) {
 *   if (!asleep) {
 *    // send requests
 *     client_request(&client, offset, n, pool, info, ids, &n_received);
 *     if (n_received == 0) asleep = true;
 *   }
 *
 *   // for each new solution
 *   client_send_solution(&client, vertices, solution_size);
 *
 *   // for each finished subproblem, *after* sending solutions
 *   client_notify_finished(&client, id);
 * }
 * client_close(&client);
 * @endcode
 */
error_code client_init(client *client, const char *server_name);

/**
 * Sends information about the size of the problem.
 *
 * Only one of the clients that connect to a given server can use this
 * command. After this, it should send its subproblems (@ref
 * client_send_subproblems) before following the same execution as other clients
 * (@ref client_receive_layout).
 *
 * @param num_vertices Maximal number of vertices in the solution
 * @param num_quads Maximal number of quads in the solution
 * @param num_hexes Maximal nubmer of hexahedra in the solution
 * @param num_symmetries Number of permutaiton in the symmetry group of the input
 * @param num_boundary_vertices Number of vertices on the boundary of the mesh
 * @param max_no_goods_size Maximum size of the nogoods in any subproblem
 * @param flags Flags to speed up the solver for certain special cases. See @ref
 *   solver_flags.
 */
error_code client_send_layout(client *client,
                              uint32_t num_vertices, uint32_t num_quads,
                              uint32_t num_hexes, uint32_t num_symmetries,
                              uint32_t num_boundary_vertices,
                              uint32_t max_no_goods_size, uint8_t flags);

/**
 * Waits for information about the size of the problem.
 *
 * This is the first function called by most clients (except for the one)
 *
 * @param [out] num_vertices
 * @param [out] num_quads
 * @param [out] num_hexes
 * @param [out] num_symmetries
 * @param [out] num_boundary_vertices
 * @param [out] max_nogoods_size
 * @param [out] flags
 */
error_code client_receive_layout(client *client,
                                 uint32_t *num_vertices, uint32_t *num_quads,
                                 uint32_t *num_hexes, uint32_t *num_symmetries,
                                 uint32_t *num_boundary_vertices,
                                 uint32_t *max_no_goods_size, uint8_t *flags);

/**
 * Sends an array of subproblems to the server.
 *
 * This is normaly called after @ref client_send_layout. After one or more calls
 * to this function, @ref client_finish_send_subproblems is called before
 * following the same code path as regular clients.
 *
 * @param offset Index of the first element of the array
 * @param n Number of subproblems to send
 * @param pool Array of subproblems
 */
error_code client_send_subproblems(client *client, uint32_t offset, uint32_t n,
                                   solver_pool *pool);

/**
 * Notifies the server that the client will no longer send any subproblems.
 *
 * This is the last function called by the client which read the input file
 * before following the same code path as regular client with @ref
 * client_receive_layout.
 */
error_code client_finish_send_subproblems(client *client);

/**
 * Requests some subproblems from the server.
 *
 * Each subproblem sent by the server comes with an index. This index must be
 * sent back to the server when notifying it that the exploration of this
 * problem has been completed (@ref client_notify_finished).
 *
 * If this function returns with @p n_received set to 0, this indicates that the
 * client should no longer be sending request messages to the server. If for
 * some reason more subproblems become available, the server will send a wake-up
 * message to the client. To handle this mechanism, the user should maintain a
 * flag, initialize it to true and set it to false when receiving 0 subproblems
 * fater a request. @ref client_idle can be used to set the flag back to true
 * upon the wake-up message:
 *
 * @code
 * bool asleep = false;
 * while (client_idle(&client, &asleep, should_stay_idle, &data)) {
 *   if (!asleep) {
 *    // send requests
 *     client_request(&client, offset, n, pool, info, ids, &n_received);
 *     if (n_received == 0) asleep = true;
 *   }
 *
 *   // do work, send solutions, notify about finished subproblems, ...
 * }
 * @endcode
 *
 * @param offset Index of the first entry to overwrite in the output array
 * @param n Maximum number of subproblems the client can accept for this request
 * @param [out] pool Array of solvers in which the requested subproblems will be
 *   stored
 * @param [out] info Additional information about received subproblems. The ith
 *   entry of this array corresponds to the ith subproblem in the pool.
 * @param [out] ids Indices of the subproblems sent by the server.
 * @param [out] n_received Number of subproblems received by the client.
 */
error_code client_request(client *client, uint32_t offset, uint32_t n,
                          solver_pool *pool,
                          uint32_t *ids,
                          uint32_t *n_received);

/**
 * Tells the server that the subproblem with index @p i has been explored.
 */
error_code client_notify_finished(client *client, uint32_t i);

/**
 * Sends a solution to the server.
 *
 * @param vertices Array containing the indices of the vertices in the
 *   solution. Each successive group of 8 element corresponds to a hexahedron.
 * @param num_hexes Number of hexahedra in the solution.
 */
error_code client_send_solution(client *client, vertex_index *vertices,
                                hex_index num_hexes);

/**
 * Waits for an event to occur.
 *
 * This function returns when @p cond returns true, when receiving a
 * message from the server, or when the connection is terminated.
 *
 * @param [out] is_asleep Flag indicating whether or not the client is allowed
 *   to send requests (@ref client_request) to the server. This function changes
 *   this flag to true if a wake-up message is received during its execution.
 * @param cond Function which returns true if the client should remain idle.
 * @param user_data Parameter passed to the cond argument.
 *
 * @return True if the connection is still active, false if the client should
 *   exit.
 */
bool client_idle(client *client, bool *is_asleep,
                 bool (*cond)(void *user_data), void *user_data);

/**
 * Closes the connection and releases all memory used by the client.
 *
 * Any call to @ref client_init should correspond to an eventual call to @ref
 * client_close.
 */
void client_close(client *client);

/**
 * @}
 */

#endif
